/*
 * Wazuh app - Specific methods to fetch Wazuh vulnerability data from Elasticsearch
 * Copyright (C) 2015-2020 Wazuh, Inc.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Find more information about this on the LICENSE file.
 */
import { ElasticWrapper } from '../lib/elastic-wrapper';
import { Base } from './base-query';
import { WAZUH_ALERTS_PATTERN } from '../../util/constants';

export class VulnerabilityRequest {
  /**
   * Constructor
   * @param {*} server Hapi.js server object provided by Kibana
   */
  constructor(server) {
    this.wzWrapper = new ElasticWrapper(server);
  }

  /**
   * Returns top 3 agents for specific severity
   * @param {Number} gte Timestamp (ms) from
   * @param {Number} lte Timestamp (ms) to
   * @param {String} severity Low, Medium, High, Critical
   * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability
   * @returns {Array<Number>}
   */
  async topAgentCount(
    gte,
    lte,
    severity,
    filters,
    pattern = WAZUH_ALERTS_PATTERN
  ) {
    try {
      const base = {};

      Object.assign(base, Base(pattern, filters, gte, lte));

      Object.assign(base.aggs, {
        '2': {
          terms: {
            field: 'agent.id',
            size: 3,
            order: {
              _count: 'desc'
            }
          }
        }
      });

      base.query.bool.must.push({
        match_phrase: {
          'data.vulnerability.severity': {
            query: severity
          }
        }
      });

      const response = await this.wzWrapper.searchWazuhAlertsWithPayload(
        base,
        this.namespace
      );
      const { buckets } = (response.aggregations || {})['2'] || {};

      return (buckets || []).map(item => item.key);
    } catch (error) {
      return Promise.reject(error);
    }
  }

  /**
   * Returns top 3 CVE
   * @param {Number} gte Timestamp (ms) from
   * @param {Number} lte Timestamp (ms) to
   * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability
   * @returns {Array<Number>}
   */
  async topCVECount(gte, lte, filters, pattern = WAZUH_ALERTS_PATTERN) {
    try {
      const base = {};

      Object.assign(base, Base(pattern, filters, gte, lte));

      Object.assign(base.aggs, {
        '2': {
          terms: {
            field: 'data.vulnerability.cve',
            size: 3,
            order: {
              _count: 'desc'
            }
          }
        }
      });

      const response = await this.wzWrapper.searchWazuhAlertsWithPayload(
        base,
        this.namespace
      );
      const { buckets } = (response.aggregations || {})['2'] || {};

      return (buckets || []).map(item => item.key);
    } catch (error) {
      return Promise.reject(error);
    }
  }

  /**
   * Returns unique count of vulnerability alerts using specific severity.
   * @param {Number} gte Timestamp (ms) from
   * @param {Number} lte Timestamp (ms) to
   * @param {String} severity Low, Medium, High, Critical
   * @param {String} filters E.g: cluster.name: wazuh AND rule.groups: vulnerability
   * @returns {Number}
   */
  async uniqueSeverityCount(
    gte,
    lte,
    severity,
    filters,
    pattern = WAZUH_ALERTS_PATTERN
  ) {
    try {
      const base = {};

      Object.assign(base, Base(pattern, filters, gte, lte));

      Object.assign(base.aggs, {
        '1': {
          cardinality: {
            field: 'agent.id'
          }
        }
      });

      base.query.bool.must.push({
        match_phrase: {
          'data.vulnerability.severity': {
            query: severity
          }
        }
      });

      const response = await this.wzWrapper.searchWazuhAlertsWithPayload(
        base,
        this.namespace
      );

      //aggregations: { '1': { value: 2 } } }
      return response &&
        response.aggregations &&
        response.aggregations['1'] &&
        response.aggregations['1'].value
        ? response.aggregations['1'].value
        : 0;
    } catch (error) {
      return Promise.reject(error);
    }
  }

  async topPackages(
    gte,
    lte,
    severity,
    filters,
    pattern = WAZUH_ALERTS_PATTERN
  ) {
    try {
      const base = {};

      Object.assign(base, Base(pattern, filters, gte, lte));

      Object.assign(base.aggs, {
        '2': {
          terms: {
            field: 'data.vulnerability.package.name',
            size: 20,
            order: {
              _count: 'desc'
            }
          }
        }
      });

      base.query.bool.must.push({
        match_phrase: {
          'data.vulnerability.severity': {
            query: severity
          }
        }
      });

      const response = await this.wzWrapper.searchWazuhAlertsWithPayload(
        base,
        this.namespace
      );
      const { buckets } = (response.aggregations || {})['2'] || {};

      return (buckets || []).map(item => ({
        package: item.key,
        severity: severity
      }));
    } catch (error) {
      return Promise.reject(error);
    }
  }

  async topPackagesWithCVE(
    gte,
    lte,
    severity,
    filters,
    pattern = WAZUH_ALERTS_PATTERN
  ) {
    try {
      const base = {};

      Object.assign(base, Base(pattern, filters, gte, lte));

      Object.assign(base.aggs, {
        '2': {
          terms: {
            field: 'data.vulnerability.package.name',
            size: 3,
            order: {
              _count: 'desc'
            }
          },
          aggs: {
            '3': {
              terms: {
                field: 'data.vulnerability.reference',
                size: 10,
                order: {
                  _count: 'desc'
                }
              }
            }
          }
        }
      });

      base.query.bool.must.push({
        match_phrase: {
          'data.vulnerability.severity': {
            query: severity
          }
        }
      });

      const response = await this.wzWrapper.searchWazuhAlertsWithPayload(
        base,
        this.namespace
      );
      const { buckets } = (response.aggregations || {})['2'] || {};

      return (buckets || []).map(item => ({
        package: item.key,
        references: item['3'].buckets.map(ref => ref.key)
      }));
    } catch (error) {
      return Promise.reject(error);
    }
  }
}
